Android Tips #35 ShareCompat で簡単に共有アクションをつくる
ShareCompat とは
ShareCompat は Activity 間のデータのやりとり (共有) のためのユーティリティクラスです。Support Package に含まれているクラスなので Android 1.6 (APIレベル4) から使用することができます。
Activity 間のデータのやりとりは Intent を使っておこないますが ShareCompat を使うことで ACTION_SEND や ACTION_SEND_MULTIPLE といった共有アクションの暗黙的 Intent をつくったり、逆に共有アクションの詳細な情報を取得するといったことを簡単に実装することができます。
Android 4.0 (APIレベル14) から共有アクションの履歴を表示する ShareActionProvider が使えるようになりましたが ShareCompat で同等の機能をバージョンごとにソースを分岐させる必要なく実装できます!
ということで、今回はこの ShareCompat を使った共有アクションのつくりかたを解説したいと思います!
ShareCompat を使って共有アクションをつくる
以下のような「オプションメニューで共有アクションの暗黙的 Intent を起動する」サンプルをつくってみます! EditText に入力された内容をデータとして取り扱っています。
1. オプションメニューをつくる
まずはトリガーになるオプションメニューを普通につくります。Android 3.0 (APIレベル11) 以上は ActionBar に常に表示しておきたいので android:showAsAction は always にしておきます。
activity_simple_send.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/menu_share" android:orderInCategory="100" android:showAsAction="always" android:title="Share" android:icon="@android:drawable/ic_menu_share" /> </menu>
2. ShareCompat.IntentBuilder をインスタンス化する
共有アクションの Intent をつくるには ShareCompat.IntentBuilder を使います。スタティックメソッド ShareCompat.IntentBuilder#from() を使ってインスタンスを取得します。引数には Intent を送出する元の Activity を渡します。
ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(MainActivity.this);
3. データをくっつける
上記で得た IntentBuilder インスタンスに遷移先の Activity に渡したいデータをくっつけます。下記のようなデータがつけられます。
setChooserTitle() | String | アプリ選択ダイアログのタイトル |
---|---|---|
setEmailTo() | String[] | メールの送信先 (TO) |
setEmailCc() | String[] | メールの送信先 (CC) |
setEmailBcc() | String[] | メールの送信先 (BCC) |
setSubject() | String | タイトル |
setText() | String | テキスト |
setHtmlText() | String | HTML テキスト |
setStream() | Uri | 画像ファイルなどのデータの URI |
setType() | String | データの種類 (mime-type) |
addEmailTo() | String または String[] | 追加するメールの送信先 (TO) |
addEmailCc() | String または String[] | 追加するメールの送信先 (CC) |
addEmailBcc() | String または String[] | 追加するメールの送信先 (BCC) |
addStream() | Uri | 追加する画像ファイルなどのデータの URI |
データの URI がひとつの場合は ACTION_SEND として、複数の場合は ACTION_SEND_MULTIPLE として Intent がつくられます。 setType() で指定した mime-type が各アプリの Activity で指定している IntentFilter とマッチしていれば暗黙的 Intent で起動できる Activity となります。
4. Intent を起動する
最後に onOptionsItemSelected() のタイミングで ShareCompat.IntentBuilder#startChooser() を呼んで Intent を起動します。
これで完成です!
SimpleSendActivity.java
package jp.classmethod.android.sample.sharecompat; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.ShareCompat; import android.view.Menu; import android.view.MenuItem; import android.widget.EditText; public class SimpleSendActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple_send); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_simple_send, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (R.id.menu_share == item.getItemId()) { EditText address = (EditText) findViewById(R.id.address_text); EditText subject = (EditText) findViewById(R.id.subject_text); EditText message = (EditText) findViewById(R.id.message_text); String[] toList = new String[] {address.getText().toString()}; // IntentBuilder をインスタンス化 ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(SimpleSendActivity.this); // データをセットする builder.setChooserTitle("Choose Send App"); builder.setEmailTo(toList); builder.setSubject(subject.getText().toString()); builder.setText(message.getText().toString()); builder.setType("text/plain"); // Intent を起動する builder.startChooser(); } return super.onOptionsItemSelected(item); } }
ActionBar のシェアボタンを押すと…
Android 2.3.3 (APIレベル10) 以前のバージョンではオプションメニューに表示されます(下図は Android 1.6 の場合)。
ShareCompat を使って共有アクションをつくる (履歴付き)
Android 4.0 から共有アクションのアプリ一覧が履歴付きで表示できる ShareActionProvider が使えるようになりましたが、同等の機能を ShareCompat で作ってみたいと思います。使いかたは上記とほぼ同じですが、処理のタイミングが少し異なるので注意しましょう。
履歴付きの共有アクションは Android 4.0 以降のみ表示されます。Android 3.2 (APIレベル13) 以前のバージョンでは通常の共有アクションとして動作します。
1. Activity#onPrepareOptionsMenu() をオーバーライドする
Activity#onPrepareOptionsMenu() はオプションメニューがつくられたときに呼ばれるメソッドです。この時点で ShareCompat.IntentBuilder をインスタンス化し、データをセットします。そして ShareCompat.configureMenuItem() で対象の MenuItem に共有アクションをセットします。
@Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); String[] toList = new String[] {mAddress.getText().toString()}; // IntentBuilder をインスタンス化 ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(CustomSendActivity.this); // データをセットする builder.setChooserTitle("Choose Send App"); builder.setEmailTo(toList); builder.setSubject(mSubject.getText().toString()); builder.setText(mMessage.getText().toString()); builder.setType("text/plain"); ShareCompat.configureMenuItem(menu, R.id.menu_share, builder); return true; }
これで ActionBar の任意のアイテムが履歴付きになりました!一度共有アクションを実行したあとは最後に使ったアプリが右側に表示されます。
2. データが更新されるようにする
上記実装では ActionBar に履歴を表示できるようになりましたが Activity の生成時にしか呼ばれないので実際は EditText などデータが書き換わるものは共有できません。あくまで onPrepareOptionsMenu() が最初に呼ばれたときに既にデータがあるものだけしか Intent にデータがセットできないです。
そんなときに登場するのが ActivityCompat#invalidateOptionsMenu() です。このメソッドを呼ぶと onPrepareOptionsMenu() がもう一度走ります。引数には表示中の Activity を渡します。 EditText で内容が書き換わったタイミングで更新したい場合は TextWatcher#onTextChanged() など、データを更新したい任意のタイミングで呼びます。
CustomSendActivity.java
package jp.classmethod.android.sample.sharecompat; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ShareCompat; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.widget.EditText; public class CustomSendActivity extends Activity { private EditText mAddress; private EditText mSubject; private EditText mMessage; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_send); mAddress = (EditText) findViewById(R.id.address_text); mSubject = (EditText) findViewById(R.id.subject_text); mMessage = (EditText) findViewById(R.id.message_text); mAddress.addTextChangedListener(new UITextWatcher()); mSubject.addTextChangedListener(new UITextWatcher()); mMessage.addTextChangedListener(new UITextWatcher()); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_send, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); String[] toList = new String[] {mAddress.getText().toString()}; // IntentBuilder をインスタンス化 ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(CustomSendActivity.this); // データをセットする builder.setChooserTitle("Choose Send App"); builder.setEmailTo(toList); builder.setSubject(mSubject.getText().toString()); builder.setText(mMessage.getText().toString()); builder.setType("text/plain"); ShareCompat.configureMenuItem(menu, R.id.menu_share, builder); return true; } private class UITextWatcher implements TextWatcher { @Override public void afterTextChanged(Editable editable) { } @Override public void beforeTextChanged(CharSequence text, int start, int count, int after) { } @Override public void onTextChanged(CharSequence text, int start, int count, int after) { ActivityCompat.invalidateOptionsMenu(CustomSendActivity.this); } } }
Intent を受け取った Activity にちゃんとデータが渡されます!例として au のEメールアプリに送ってみました。
ShareCompat で共有アクションの情報を取得する
ShareCompat では共有がリクエストされた Activity で共有アクションの情報を取得することができます。実装はとてもシンプルで ShareCompat.IntentReader をインスタンス化して getText() などのようなメソッドでデータを取得するだけです。アプリケーション名や Activity 名、アプリアイコンなども取得できるのでいろいろ使えそうですね。ただし呼び出し元のアプリケーション情報は必ずしも受け取れるわけではないので注意しましょう。
RecieveActivity.java
package jp.classmethod.android.sample.sharecompat;
import android.app.Activity; import android.content.ComponentName; import android.os.Bundle; import android.support.v4.app.ShareCompat; import android.widget.ImageView; import android.widget.TextView;
public class RecieveActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recieve);
// ShareCompat.IntentReader をインスタンス化 ShareCompat.IntentReader reader = ShareCompat.IntentReader.from(RecieveActivity.this);
// アプリアイコン ImageView appImageView = (ImageView) findViewById(R.id.app_image_view); appImageView.setImageDrawable(reader.getCallingApplicationIcon()); // Activity アイコン ImageView activityImageView = (ImageView) findViewById(R.id.activity_image_view); activityImageView.setImageDrawable(reader.getCallingActivityIcon());
// 呼び出し元の Activity String callingActivity = "null"; ComponentName componentName = reader.getCallingActivity(); if (componentName != null) { callingActivity = componentName.getClassName(); }
// 各データを読み込んで TextView に表示する String detail = "CallingApplicationLabel=" + reader.getCallingApplicationLabel(); detail += "\n" + "CallingPackage=" + reader.getCallingPackage(); detail += "\n" + "CallingActivity=" + callingActivity; detail += "\n" + "Type=" + reader.getType(); detail += "\n" + "Subject=" + reader.getSubject(); detail += "\n" + "Text=" + reader.getText(); detail += "\n" + "HtmlText=" + reader.getHtmlText(); String [] emailToList = reader.getEmailTo(); if (emailToList != null) { for (String emailTo : emailToList) { detail += "\n" + "EmailTo=" + emailTo; } } else { detail += "\n" + "EmailTo=null"; } String [] emailCcList = reader.getEmailCc(); if (emailCcList != null) { for (String emailCc : emailCcList) { detail += "\n" + "EmailCc=" + emailCc; } } else { detail += "\n" + "EmailCc=null"; } String [] emailBccList = reader.getEmailBcc(); if (emailBccList != null) { for (String emailBcc : emailBccList) { detail += "\n" + "EmailBcc=" + emailBcc; } } else { detail += "\n" + "EmailBcc=null"; } int streamCount = reader.getStreamCount(); detail += "\n" + "StreamCount=" + streamCount; for (int i = 0; i < streamCount; i++) { detail += "\n" + "Stream=" + reader.getStream(i); } TextView textView = (TextView) findViewById(R.id.text_view); textView.setText(detail); } } [/java]
AndroidManifest に対象の Activity に IntentFilter を登録するのを忘れずに。
<activity android:name="jp.classmethod.android.sample.sharecompat.RecieveActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="*/*" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND_MULTIPLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="*/*" /> </intent-filter> </activity>
では実行してみます。先ほどと同じアプリにしてあるので共有アクションでこのアプリを選ぶと情報が表示されます。
Google Chrome から弊社ブログを共有してみました。
取得できる内容がかなり少ないですね…。ギャラリーとかでもやってみましたが同じような感じでした。
どうやらアプリ名やらアイコンやらが取得できるアプリはかなり少なそうです。
ソースコード
今回実装したソースコードを github で公開しました。ぜひ実装時の参考にしてください!
まとめ
いままでは Intent を普通に作って startActivity() していたと思いますが ShareCompat を使うと処理がきれいにまとまりました。また ShareActionProvider を使わずにバージョン間で処理を分岐することなく実装できるのはとても良いですね!